////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNFileIO/inc/WNFile.h"
#include "WNMemory/inc/WNMemory.h"

#ifdef _WN_WINDOWS
    #include <windows.h>
#else
    #include <stdio.h>
#endif

#ifdef _WN_LINUX
    #include <sys/file.h>
#endif

using namespace WNFileIO;

WNFile::WNFile() :
    mMaxBufferSize(0),
    mFileSize(0),
    mFileBuffer(WN_NULL),

    #ifdef _WN_WINDOWS
        mFileHandle(INVALID_HANDLE_VALUE),
    #else
        mFileHandle(WN_NULL),
    #endif

    mIsFileOpen(WN_FALSE) {
}

WNFile::~WNFile() {
    if (mFileBuffer) {
        if (mFileMode & WNFile::WN_WRITE) {
            CommitFileBuffer();
        }

        WNFree(mFileBuffer);
    }

    #ifdef _WN_WINDOWS
        if (mFileHandle) {
            CloseHandle(mFileHandle);
        }
    #else
        if (mFileHandle) {
            fclose(static_cast<FILE*>(mFileHandle));
        }
    #endif
}

WNFile::WNFileError WNFile::OpenFile(const WN_CHAR* _name, WN_UINT32 _mode) {
    WN_DEBUG_ASSERT(!mIsFileOpen);

    if ((_mode & (WNFile::WN_CREATE | WNFile::WN_CLOBBER)) == WNFile::WN_CLOBBER) {
        return(WNFile::WN_FILE_BAD_MODE);
    }

    const WN_BOOL shouldCheckForFile = !(_mode & WNFile::WN_CREATE);

    if (shouldCheckForFile && !DoesFileExist(_name)) {
        return(WNFile::WN_FILE_NON_EXISTANT);
    }

    #ifdef _WN_WINDOWS
        DWORD accessType = 0;

        accessType |= ((_mode & WNFile::WN_READ) ? GENERIC_READ : 0);
        accessType |= ((_mode & WNFile::WN_WRITE) ? GENERIC_WRITE : 0);

        const DWORD sharing = (_mode & WNFile::WN_EXCLUSIVE) ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE);

        DWORD creationDisposition = (_mode & WNFile::WN_CREATE) ? OPEN_ALWAYS : OPEN_EXISTING;

        if (_mode & WNFile::WN_CLOBBER) {
            creationDisposition = CREATE_ALWAYS;
        }

        mFileHandle = CreateFile(_name, accessType, sharing, WN_NULL, creationDisposition, 0, 0);

        if (mFileHandle == INVALID_HANDLE_VALUE) {
            return(WNFile::WN_FILE_BAD_MODE);
        }

        LARGE_INTEGER filesize;

        if (!GetFileSizeEx(mFileHandle, &filesize)) {
            CloseHandle(mFileHandle);

            return(WNFile::WN_FILE_BAD_MODE);
        }

        if (static_cast<WN_SIZE_T>(filesize.QuadPart) != filesize.QuadPart) {
            CloseHandle(mFileHandle);

            return(WNFile::WN_FILE_TOO_LARGE);
        }

        mFileSize = static_cast<WN_SIZE_T>(filesize.QuadPart);
    #else
        const WN_CHAR* modestring;

        if (_mode & WNFile::WN_CLOBBER) {
            if (!_mode & WNFile::WN_READ) {
                modestring = "w";
            } else {
                modestring = "r+";
            }
        } else if ((_mode & WNFile::WN_READ) && (_mode & WNFile::WN_WRITE)) {
            modestring = "r+";
        } else if ((_mode & WNFile::WN_READ)) {
            modestring = "r";
        } else if ((_mode & WNFile::WN_WRITE)) {
            modestring = "a";
        }

        mFileHandle = fopen(_name, modestring);

        if (!mFileHandle) {
            return(WNFile::WN_FILE_ERROR);
        }

        fseek(mFileHandle, 0L, SEEK_END);

        mFileSize = ftell(mFileHandle);

        fseek(mFileHandle, 0L, SEEK_SET);
        
    #endif

    mIsFileOpen = WN_TRUE;
    mFileMode = _mode;

    return(WNFile::WN_FILE_OK);
}

WN_BOOL WNFile::DoesFileExist(const WN_CHAR* _name) {
    #ifdef _WN_WINDOWS
        const DWORD fileAttributes = GetFileAttributes(_name);

        if (fileAttributes == INVALID_FILE_ATTRIBUTES) {
            return(WN_FALSE);
        }

        if ((fileAttributes & FILE_ATTRIBUTE_NORMAL) == fileAttributes) {
            return(WN_TRUE);
        }

        if ((fileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
            (fileAttributes & FILE_ATTRIBUTE_DEVICE) ||
            (fileAttributes & FILE_ATTRIBUTE_OFFLINE) ||
            (fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
            (fileAttributes & FILE_ATTRIBUTE_SYSTEM)) {
                return(WN_FALSE);
        }
    #else
        FILE* file;

        if ((file = fopen(_name, "r")) == WN_NULL) {
            fclose(file);

            return(WN_FALSE);
        }

        fclose(file);
    #endif

    return(WN_TRUE);
}

WN_CHAR* WNFile::getDataBuffer() {
    if (mFileBuffer != WN_NULL || mFileSize == 0) {
        return(mFileBuffer);
    }

    mFileBuffer = static_cast<WN_CHAR*>(WNMalloc(mFileSize));

    if (mFileBuffer == WN_NULL) {
        return(WN_NULL);
    }

    #ifdef _WN_WINDOWS
        WN_SIZE_T readLeft = mFileSize;

        do {
            const DWORD maxRead = (static_cast<DWORD>(readLeft) == readLeft) ? static_cast<DWORD>(readLeft) : -1;

            DWORD amountRead = 0;

            if (!ReadFile(mFileHandle,
                          mFileBuffer + (mFileSize - readLeft),
                          maxRead,
                          &amountRead,
                          WN_NULL)) {
                    WNFree(mFileBuffer);

                    return(WN_NULL);
            }

            readLeft -= amountRead;
        } while(readLeft > 0);
    #else
        if (fread(mFileBuffer, sizeof(WN_CHAR), mFileSize, static_cast<FILE*>(mFileHandle)) != mFileSize) {
            WNFree(mFileBuffer);

            return(WN_NULL);
        }
    #endif

    return(mFileBuffer);
}

WN_CHAR* WNFile::ResizeDataBuffer(WN_SIZE_T _size) {
    if (mFileBuffer) {
        mFileBuffer = static_cast<WN_CHAR*>(WNRealloc(mFileBuffer, sizeof(WN_CHAR) * _size));
    } else {
        mFileBuffer = static_cast<WN_CHAR*>(WNMalloc(sizeof(WN_CHAR) * _size));
    }

    mFileSize = _size;

    if (mFileSize > 0) {
        WN_RELEASE_ASSERT(mFileBuffer);
    }

    return(mFileBuffer);
}

WN_BOOL WNFile::CommitFileBuffer() {
    WN_DEBUG_ASSERT(mFileBuffer != WN_NULL);

    if (!(mFileMode & WNFile::WN_WRITE)) {
        return(WN_FALSE);
    }

    #ifdef _WN_WINDOWS
        WN_SIZE_T writeLeft = mFileSize;

        do {
            const DWORD maxWrite = (static_cast<DWORD>(writeLeft) == writeLeft) ? static_cast<DWORD>(writeLeft): -1;

            DWORD amountWrote = 0;

            if (WriteFile(mFileHandle,
                          mFileBuffer + (mFileSize - writeLeft),
                          maxWrite,
                          &amountWrote,
                          WN_NULL)) {
                    return(WN_FALSE);
            }

            writeLeft -= amountWrote;
        } while(writeLeft > 0);
    #else
        if (fwrite(mFileBuffer, sizeof(WN_CHAR), mFileSize, static_cast<FILE*>(mFileHandle)) != mFileSize) {
            return(WN_FALSE);
        }

        if (fflush(static_cast<FILE*>(mFileHandle)) == EOF) {
            return(WN_FALSE);
        }
    #endif

    return(WN_TRUE);
}
